package pentago.engine;

import java.awt.Point;
import java.util.ArrayList;

import pentago.exceptions.PentagoInvalidPlatePositionException;
import pentago.exceptions.PentagoInvalidPositionException;
import pentago.exceptions.PentagoInvalidRotationException;

public class GameState {

    private static final long HORIZONTAL_KILLER_MASK = 33;
    private static final long HORIZONTAL_FIVES_MASK = 31;
    private static final long HORIZONTAL_FOURS_MASK = 15;
    private static final long HORIZONTAL_THREES_MASK = 7;

    private static final long VERTICAL_KILLER_MASK = ((long) 1073741825) << 5;
    private static final long VERTICAL_FIVES_MASK = ((long) 0x20820800)
	    | (((long) 0x20820800) << 6);
    private static final long VERTICAL_FOURS_MASK = ((long) 0x20820000)
	    | (((long) 0x20820000) << 6);
    private static final long VERTICAL_THREES_MASK = ((long) 0x20800000)
	    | (((long) 0x20800000) << 6);

    // Diagonal mask direction: \
    private static final long DIAGONAL_KILLER_MASK1 = (((long) 1) << 35) + 1;
    private static final long DIAGONAL_FIVES_MASK1 = 0x10204081;
    private static final long DIAGONAL_FOURS_MASK1 = 0x204081;
    private static final long DIAGONAL_THREES_MASK1 = 0x4081;

    // Diagonal mask direction: /
    private static final long DIAGONAL_KILLER_MASK2 = (((long) 1) << 30) + 32;
    private static final long DIAGONAL_FIVES_MASK2 = 0x01084210;
    private static final long DIAGONAL_FOURS_MASK2 = 0x42108;
    private static final long DIAGONAL_THREES_MASK2 = 0x1084;

    public static final int FIVE_IN_ROW_SCORE = 1000000;
    public static final int FOUR_IN_ROW_SCORE = 1000;
    public static final int KILLER_MOVE_SCORE = (int) (FOUR_IN_ROW_SCORE * 20);
    public static final int THREE_IN_ROW_SCORE = 100;
    public static final int MARBLE_IN_MIDDLE_SCORE = 5;
    public static final int MARBLE_IN_PLATE_CENTRE_SCORE = 5;

    private BitBoard bitBoard;
    private String hashKey;
    private byte currentPlayer;
    private int score;
    private int marblesCount;
    private boolean evaluated;
    private boolean terminalityDefined;
    private boolean isTerminal;
    private boolean hashKeyGenerated;

    public GameState() {
	this(new BitBoard());
    }

    public GameState(BitBoard bitBoard) {
	this.bitBoard = bitBoard;
	determineParams(bitBoard);
	evaluated = false;
	terminalityDefined = false;
	hashKeyGenerated = false;
    }

    public GameState(byte[][] boardState) {
	this(new BitBoard(boardState));
    }

    public String generateHashKey() {
	if (!hashKeyGenerated) {
	    hashKey = String.valueOf(bitBoard.firstBoard)
		    + String.valueOf(bitBoard.secondBoard);
	    hashKeyGenerated = true;
	}
	return hashKey;
    }

    public int evaluate() {

	if (evaluated) {
	    return score;
	}

	evaluated = true;
	score = 0;
	int firstFives, secondFives;
	int modifier = currentPlayer == GameEngine.FIRST_PLAYER ? 1 : -1;

	firstFives = countFives(bitBoard.firstBoard);
	secondFives = countFives(bitBoard.secondBoard);
	score += modifier * (firstFives - secondFives) * FIVE_IN_ROW_SCORE;

	if (score >= FIVE_IN_ROW_SCORE / 2 || score <= -FIVE_IN_ROW_SCORE / 2)
	    return score;

	score += modifier
		* (countFours(bitBoard.firstBoard) - countFours(bitBoard.secondBoard))
		* FOUR_IN_ROW_SCORE;

	score += modifier
		* (countThrees(bitBoard.firstBoard) - countThrees(bitBoard.secondBoard))
		* THREE_IN_ROW_SCORE;

	score += modifier
		* (countInner(bitBoard.firstBoard) - countInner(bitBoard.secondBoard))
		* MARBLE_IN_MIDDLE_SCORE;

	score += modifier
		* (countMiddle(bitBoard.firstBoard) - countMiddle(bitBoard.secondBoard))
		* MARBLE_IN_PLATE_CENTRE_SCORE;

	return score;
    }

    public ArrayList<Move> generateIncompleteMoves() {

	ArrayList<Move> result = new ArrayList<Move>(36 - marblesCount);
	long occupiedPositions = bitBoard.firstBoard | bitBoard.secondBoard;

	long mask = 1;
	for (int y = 5; y >= 0; --y) {
	    for (int x = 5; x >= 0; --x) {
		if ((occupiedPositions & mask) == 0) {
		    try {
			result.add(new Move(new Point(x, y),
				GameBoard.UNKNOWN_POSITION,
				Move.UNKNOWN_ROTATION_DIRECTION));
		    } catch (PentagoInvalidPositionException e) {
			e.printStackTrace();
		    } catch (PentagoInvalidRotationException e) {
			e.printStackTrace();
		    } catch (PentagoInvalidPlatePositionException e) {
			e.printStackTrace();
		    }
		}
		mask <<= 1;
	    }
	}

	return result;
    }

    public GameState nextStateFromMove(Move move) {

	BitBoard newBitBoard = new BitBoard(this.bitBoard);
	int position = (5 - move.getPosition().y) * 6
		+ (6 - move.getPosition().x);

	// assert position > 0 && position < 37;

	newBitBoard.setPoistion(position, currentPlayer);

	if (move.getRotatedPlate() != GameBoard.UNKNOWN_POSITION
		&& move.getRotation() != Move.UNKNOWN_ROTATION_DIRECTION) {
	    newBitBoard.rotatePlateExperiment(move.getRotatedPlate(),
		    move.getRotation());
	}

	return new GameState(newBitBoard);
    }

    public boolean isTerminal() {
	if (!terminalityDefined) {
	    terminalityDefined = true;
	    isTerminal = countFives(bitBoard.firstBoard) != 0
		    || countFives(bitBoard.secondBoard) != 0
		    || marblesCount == 36;
	}
	return isTerminal;
    }

    private int countFives(long playerMarbles) {

	int fivesCount = 0;
	long tempMask = VERTICAL_FIVES_MASK;
	for (int i = 0; i < 12; ++i) {
	    if ((playerMarbles & tempMask) == tempMask)
		++fivesCount;
	    tempMask >>= 1;
	}

	tempMask = HORIZONTAL_FIVES_MASK;
	for (int i = 0; i < 6; ++i) {
	    if ((playerMarbles & tempMask) == tempMask)
		++fivesCount;
	    tempMask <<= 1;
	    if ((playerMarbles & tempMask) == tempMask)
		++fivesCount;
	    tempMask <<= 5;
	}

	tempMask = DIAGONAL_FIVES_MASK1;
	for (int i = 0; i < 2; ++i) {
	    if ((playerMarbles & tempMask) == tempMask)
		++fivesCount;
	    tempMask <<= 1;
	    if ((playerMarbles & tempMask) == tempMask)
		++fivesCount;
	    tempMask <<= 5;
	}

	tempMask = DIAGONAL_FIVES_MASK2;
	for (int i = 0; i < 2; ++i) {

	    if ((playerMarbles & tempMask) == tempMask)
		++fivesCount;
	    tempMask <<= 1;
	    if ((playerMarbles & tempMask) == tempMask)
		++fivesCount;
	    tempMask <<= 5;
	}
	return fivesCount;
    }

    private int countFours(long playerMarbles) {

	int foursCount = 0;
	long oponentMarbles = playerMarbles == bitBoard.firstBoard ? bitBoard.secondBoard
		: bitBoard.firstBoard;
	long tempMask = VERTICAL_FOURS_MASK;
	long tempKillerMask = VERTICAL_KILLER_MASK;
	for (int i = 0; i < 18; ++i) {
	    if ((playerMarbles & tempMask) == tempMask) {
		++foursCount;
		if (currentPlayer == GameEngine.FIRST_PLAYER) {
		    if (i >= 6 && i <= 11
			    && (oponentMarbles & tempKillerMask) == 0) {
			foursCount += KILLER_MOVE_SCORE / FOUR_IN_ROW_SCORE;
		    }
		} else {
		    if (i >= 6 && i <= 11
			    && (oponentMarbles & tempKillerMask) == 0) {
			foursCount += KILLER_MOVE_SCORE / FOUR_IN_ROW_SCORE;
		    }
		}
	    }
	    if (i >= 6 && i <= 11)
		tempKillerMask >>= 1;
	    tempMask >>= 1;
	}

	tempMask = HORIZONTAL_FOURS_MASK;
	tempKillerMask = HORIZONTAL_KILLER_MASK;
	for (int i = 0; i < 6; ++i) {
	    if ((playerMarbles & tempMask) == tempMask)
		++foursCount;
	    tempMask <<= 1;
	    if ((playerMarbles & tempMask) == tempMask) {
		++foursCount;
		if ((oponentMarbles & tempKillerMask) == 0) {
		    foursCount += KILLER_MOVE_SCORE / FOUR_IN_ROW_SCORE;
		}
		tempKillerMask <<= 6;
	    }
	    tempMask <<= 1;
	    if ((playerMarbles & tempMask) == tempMask)
		++foursCount;
	    tempMask <<= 4;
	}

	tempMask = DIAGONAL_FOURS_MASK1;
	tempKillerMask = DIAGONAL_KILLER_MASK1;
	for (int i = 0; i < 3; ++i) {
	    if ((playerMarbles & tempMask) == tempMask)
		++foursCount;
	    tempMask <<= 1;
	    if ((playerMarbles & tempMask) == tempMask) {
		++foursCount;
		if (i == 1 && (oponentMarbles & tempKillerMask) == 0)
		    foursCount += KILLER_MOVE_SCORE / FOUR_IN_ROW_SCORE;
	    }
	    tempMask <<= 1;
	    if ((playerMarbles & tempMask) == tempMask)
		++foursCount;
	    tempMask <<= 4;
	}

	tempMask = DIAGONAL_FOURS_MASK2;
	tempKillerMask = DIAGONAL_KILLER_MASK2;
	for (int i = 0; i < 3; ++i) {

	    if ((playerMarbles & tempMask) == tempMask)
		++foursCount;
	    tempMask <<= 1;
	    if ((playerMarbles & tempMask) == tempMask) {
		++foursCount;
		if (i == 1 && (oponentMarbles & tempKillerMask) == 0)
		    foursCount += KILLER_MOVE_SCORE / FOUR_IN_ROW_SCORE;
	    }
	    tempMask <<= 1;
	    if ((playerMarbles & tempMask) == tempMask)
		++foursCount;
	    tempMask <<= 4;
	}
	return foursCount;
    }

    private int countThrees(long playerMarbles) {

	int threesCount = 0;
	long tempMask = VERTICAL_THREES_MASK;
	for (int i = 0; i < 24; ++i) {
	    if ((playerMarbles & tempMask) == tempMask)
		++threesCount;
	    tempMask >>= 1;
	}

	tempMask = HORIZONTAL_THREES_MASK;
	for (int i = 0; i < 6; ++i) {
	    if ((playerMarbles & tempMask) == tempMask)
		++threesCount;
	    tempMask <<= 1;
	    if ((playerMarbles & tempMask) == tempMask)
		++threesCount;
	    tempMask <<= 1;
	    if ((playerMarbles & tempMask) == tempMask)
		++threesCount;
	    tempMask <<= 1;
	    if ((playerMarbles & tempMask) == tempMask)
		++threesCount;
	    tempMask <<= 3;
	}

	tempMask = DIAGONAL_THREES_MASK1;
	for (int i = 0; i < 4; ++i) {
	    if ((playerMarbles & tempMask) == tempMask)
		++threesCount;
	    tempMask <<= 1;
	    if ((playerMarbles & tempMask) == tempMask)
		++threesCount;
	    tempMask <<= 1;
	    if ((playerMarbles & tempMask) == tempMask)
		++threesCount;
	    tempMask <<= 1;
	    if ((playerMarbles & tempMask) == tempMask)
		++threesCount;
	    tempMask <<= 3;
	}

	tempMask = DIAGONAL_THREES_MASK2;
	for (int i = 0; i < 4; ++i) {
	    if ((playerMarbles & tempMask) == tempMask)
		++threesCount;
	    tempMask <<= 1;
	    if ((playerMarbles & tempMask) == tempMask)
		++threesCount;
	    tempMask <<= 1;
	    if ((playerMarbles & tempMask) == tempMask)
		++threesCount;
	    tempMask <<= 1;
	    if ((playerMarbles & tempMask) == tempMask)
		++threesCount;
	    tempMask <<= 3;
	}
	return threesCount;
    }

    private int countInner(long playerMarbles) {

	int innerCount = 0;
	long mask = 0x80;

	for (int i = 0; i < 4; ++i) {
	    if ((playerMarbles & mask) == mask)
		++innerCount;
	    mask <<= 1;
	    if ((playerMarbles & mask) == mask)
		++innerCount;
	    mask <<= 1;
	    if ((playerMarbles & mask) == mask)
		++innerCount;
	    mask <<= 1;
	    if ((playerMarbles & mask) == mask)
		++innerCount;
	    mask <<= 3;
	}

	return innerCount;
    }

    private int countMiddle(long playerMarbles) {

	int middleCount = 0;
	long mask = 0x80;

	for (int i = 0; i < 2; ++i) {
	    if ((playerMarbles & mask) == mask)
		++middleCount;
	    mask <<= 3;
	    if ((playerMarbles & mask) == mask)
		++middleCount;
	    mask <<= 15;
	}

	return middleCount;
    }

    private void determineParams(BitBoard bitBoard) {

	long firstPlayerMarbles = bitBoard.firstBoard;
	long secondPlayerMarbles = bitBoard.secondBoard;
	long mask = 1;
	int firstCount = 0;
	int secondCount = 0;
	int difference = 0;

	for (int i = 0; i < 36; ++i) {
	    if ((mask & firstPlayerMarbles) != 0) {
		++firstCount;
	    }
	    mask <<= 1;
	}

	mask = 1;
	for (int i = 0; i < 36; ++i) {
	    if ((mask & secondPlayerMarbles) != 0) {
		++secondCount;
	    }
	    mask <<= 1;
	}

	marblesCount = firstCount + secondCount;
	difference = firstCount - secondCount;
	// assert difference <= 1 && difference >= 0;
	if (difference < 0)
	    System.out.println("Cos nie tak");
	if (difference != 0) {
	    currentPlayer = GameEngine.SECOND_PLAYER;
	} else {
	    currentPlayer = GameEngine.FIRST_PLAYER;
	}
    }

    public int getMarblesCount() {
	return marblesCount;
    }

}
